using FileTransfer.Client.Utils;

namespace FileTransfer.Client.Upload;

public class FileChunkPacker
{
    private readonly int _chunkSize;
    private readonly FileInfo? _file;
    private readonly Func<Stream>? _getFile;
    private readonly long _totalSize;
    private readonly string _fileName;
    private readonly string _fileMd5;

    public FileChunkPacker(string fileName, Func<Stream> getFile, int chunkSize)
    {
        _getFile = getFile;
        _file = null;
        _chunkSize = chunkSize;
        _fileName = fileName;

        // Calculate the entire file's MD5
        using (var fs = getFile())
        {
            _totalSize = fs.Length;
            _fileMd5 = Md5.CalcFile(fs);
        }
    }

    public FileChunkPacker(FileInfo file, int chunkSize)
    {
        _file = file;
        _totalSize = file.Length;
        _fileName = file.Name;
        _getFile = null;
        _chunkSize = chunkSize;

        // Calculate the entire file's MD5
        _fileMd5 = Md5.CalcFile(file);
    }

    public int ChunkSize => _chunkSize;
    public FileInfo? File => _file;
    public long TotalSize => _totalSize;
    public string FileName => _fileName;
    public string FileMd5 => _fileMd5;

    // Get the number of chunks
    public long GetChunkCount()
    {
        return (_totalSize + _chunkSize - 1) / _chunkSize;
    }

    // Prepare for chunking
    public ICloseableIterator<FileChunk> Prepare()
    {
        // Open the file
        Stream fs;
        try
        {
            if (_file == null)
            {
                fs = _getFile!();
            }
            else
            {
                fs = _file.OpenRead();
            }
        }
        catch (Exception e)
        {
            throw new InvalidOperationException("Failed to open file stream", e);
        }

        return new FileChunkIterator(fs, _chunkSize, _totalSize);
    }

    // Iterator interface with auto-close support
    public interface ICloseableIterator<T> : IEnumerator<T>, IDisposable { }

    // Implementation of the iterator for file chunks
    private class FileChunkIterator(Stream fileStream, int chunkSize, long totalSize)
        : ICloseableIterator<FileChunk>
    {
        private readonly long _totalSize = totalSize;
        private int _chunkIndex = 0;
        private long _remainingLength = totalSize;
        private long _offset = 0;
        private FileChunk? _current;

        public FileChunk Current => _current!;

        object System.Collections.IEnumerator.Current => Current;

        public void Dispose()
        {
            fileStream.Dispose();
        }

        public bool MoveNext()
        {
            if (_remainingLength <= 0)
                return false;

            try
            {
                // Calculate the current chunk size
                var currentChunkSize = (int)Math.Min(chunkSize, _remainingLength);

                // Read the current chunk data
                var chunkData = new byte[currentChunkSize];
                var currentOffset = _offset;

                var bytesRead = fileStream.Read(chunkData, 0, currentChunkSize);
                if (bytesRead < currentChunkSize)
                {
                    Array.Resize(ref chunkData, bytesRead);
                    currentChunkSize = bytesRead;
                    _remainingLength = 0; // End of file reached earlier than expected
                }

                _offset += bytesRead;

                // Calculate the current chunk MD5
                var chunkMD5 = Md5.CalcBytes(chunkData);

                // Update remaining bytes
                _remainingLength -= bytesRead;

                // Set current chunk
                _current = new FileChunk(_chunkIndex++, currentOffset, chunkData, chunkMD5);

                return true;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException("Failed to read chunk data", e);
            }
        }

        public void Reset()
        {
            throw new NotSupportedException();
        }
    }
}
